<template>
  <div class="container flow-model-container flow-model-container-editable"
       ref="container">
    <svg :id="containerID" :style="svgStyle" ref="FlowModel" class="flow-image">
      <g/>
    </svg>
    <ContextModal ref="ContextModal"
                  @contextItemClick="contextItemClick">
      <template v-slot:plus>
        <el-icon>
          <Plus/>
        </el-icon>
      </template>

      <template v-slot:share>
        <el-icon>
          <Share/>
        </el-icon>
      </template>

      <template v-slot:remove>
        <el-icon>
          <DeleteFilled/>
        </el-icon>
      </template>

      <template v-slot:setting>
        <el-icon>
          <Setting/>
        </el-icon>
      </template>
    </ContextModal>

    <UserTaskModal ref="UserTaskModal" @addUserTask="addUserTask"/>
    <LineFormulaModal ref="LineFormulaModal" @formulaEdited="lineFormulaModalOK"/>
    <UserTaskPropertiesModal ref="UserTaskPropertiesModal" @ok="userTaskPropertiesModalOK"/>
  </div>
</template>

<script>
import ContextModal from '@/components/flow/ContextModal';
import UserTaskModal from '@/components/flow/UserTaskModal.vue';
import {guid, isEmpty, isNotEmpty} from '@/utils/CommonUtils';
import LineFormulaModal from '@/components/flow/LineFormulaModal.vue';
import UserTaskPropertiesModal from '@/components/flow/UserTaskPropertiesModal';
import {DefaultFormDefinition} from '@/components/form/DefaultFormDefinition';

export default {
  name: 'FlowModel',
  components: {
    ContextModal,
    UserTaskModal,
    LineFormulaModal,
    UserTaskPropertiesModal,
  },
  data() {
    return {
      containerID: 'flow' + guid(),
      svgWidth: 200,
      svgHeight: 200,
      flowModel: {
        "id": "flow-2",
        "start": "start",
        "end": "end",
        "nodes": [],
        "lines": [
          {
            start: 'start',
            end: 'end'
          }
        ],
        "gatewayPairs": []
      },
      renderContext: {
        svg: null,
        inner: null,
        render: null,
        graph: null
      },
      startContext: [
        {title: '增加任务', id: 'createNode', icon: 'plus'}
      ],
      emptyNodeContext: [
        {title: '删除', id: 'removeNode', icon: 'remove'}
      ],
      userTaskContext: [
        {title: '增加任务', id: 'createNode', icon: 'plus'},
        {title: '插入任务', id: 'insertNode', icon: 'plus'},
        {title: '增加分支', id: 'addParallelNode', icon: 'share'},
        {title: '增加空分支', id: 'addEmptyNode', icon: 'share'},
        {title: '属性', id: 'propertyConfig', icon: 'setting'},
        {title: '删除', id: 'removeNode', icon: 'remove'},
      ],
      endContext: [
        {title: '插入任务', id: 'createNode', icon: 'plus'}
      ],
      clickingLine: null,
      formDefinition: JSON.parse(JSON.stringify(DefaultFormDefinition)),
      clickingNode: null,
      nodeAction: null
    };
  },
  mounted() {
    this.resolveContainerStyle();
    this.renderFlowModel();
  },
  methods: {
    print() {
      this.resolveContainerStyle();
      console.log(JSON.stringify(this.flowModel));
      this.renderFlowModel();
    },
    resolveContainerStyle() {
    },
    setFormDefinition(formDefinition) {
      this.formDefinition = formDefinition;
    },
    setData(data) {
      this.flowModel = data;
      this.$nextTick(() => {
        this.renderFlowModel();
      });
    },
    getData() {
      return this.flowModel;
    },
    renderFlowModel() {
      this.drawBackground();
      this.renderContext = this.prepareDraw();
      this.drawNodes(this.renderContext);

      this.drawLines(this.renderContext);

      let {inner, render, graph} = this.renderContext;
      inner.call(render, graph);

      this.$nextTick(this.bindActions);
      this.resolveSvgSize();
    },
    tryOrderLines() {
      try {
        let res = [];
        this.doTryOrderLines(res, 'start');
        this.flowModel.lines = res;
      } catch (e) {
        console.error(e);
      }
    },
    doTryOrderLines(res, nodeId) {
      let lines = this.flowModel.lines.filter(l => l.start === nodeId && isEmpty(res.find(r => r.id === l.id)));
      if (isEmpty(lines)) return;
      for (let i = 0; i < lines.length; i++) {
        res.push(lines[i]);
        this.doTryOrderLines(res, lines[i].end);
      }
    },
    resolveSvgSize() {
      let nodeIds = this.renderContext.graph.nodes();
      let width = -1, height = -1;
      for (let i = 0; i < nodeIds.length; i++) {
        let node = this.renderContext.graph.node(nodeIds[i]);
        let {x, y} = node;
        width = Math.max(width, x);
        height = Math.max(height, y);
      }
      width += 200;
      height += 200;

      this.svgHeight = height;
      this.svgWidth = width;
    },
    prepareDraw() {
      let svg = d3.select(`#${this.containerID}`);
      let inner = svg.select("g");

      let graph = new dagreD3.graphlib.Graph();
      graph.setGraph({
        rankdir: "LR",
        marginx: 20,
        marginy: 20
      });
      return {
        svg,
        inner,
        render: new dagreD3.render(),
        graph
      };
    },
    drawLines({graph}) {
      this.tryOrderLines();
      let lines = this.flowModel.lines;
      for (let i = 0; i < lines.length; i++) {
        let label = '';
        if (this.isGateWayStart({id: lines[i].start})) {
          label = '设置条件';
          label = `<span class="line-formula-text line-formula-config-icon"
                    data-line-id="${lines[i].id}">
                      ${this.getFormulaConfigIcon(lines[i].id)}
                  </span>`;
        }

        if (isNotEmpty(lines[i].formula)) {
          label = `<span class="line-formula-text line-formula-config-icon"
                    data-line-id="${lines[i].id}">
                    ${lines[i].formula.formulaTitle}
                  </span>`;
        }
        let line = {
          labelType: "html",
          label
        };

        this.resolveLineStyle(line, lines[i]);
        graph.setEdge(lines[i].start, lines[i].end, line);
      }
    },
    resolveLineStyle(line, lineDefinition) {
    },
    getFormulaConfigIcon(id) {
      return `<i class="el-icon" data-line-id="${id}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" data-line-id="${id}"><path fill="currentColor" d="M352 480h320a32 32 0 1 1 0 64H352a32 32 0 0 1 0-64z"></path><path fill="currentColor" d="M480 672V352a32 32 0 1 1 64 0v320a32 32 0 0 1-64 0z"></path><path fill="currentColor" d="M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768zm0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896z"></path></svg></i>`;
    },
    doCreateEndNode: function (node, graph) {
      node = this.createEndNode();
      let lines = this.flowModel.lines;
      if(isEmpty(lines)) return ;
      let flag = false;
      lines.forEach(line => {
        if (line.end === 'end') {
          flag = true;
        }
      })
      if (flag) {
        graph.setNode(node.id, node.node);
      }
      return node;
    },
    drawNodes({graph}) {
      let node = this.createStartNode();
      graph.setNode(node.id, node.node);
      this.resolveNodeStyle(node);
      node = this.doCreateEndNode(node, graph);
      this.resolveNodeStyle(node);

      let nodes = this.flowModel.nodes;
      if (nodes && nodes.length) {
        for (let i = 0; i < nodes.length; i++) {
          node = this.createNodeGraphByNode(nodes[i]);
          this.resolveNodeStyle(node, nodes[i]);
          if (node) {
            graph.setNode(node.id, node.node);
          }
        }
      }
    },
    resolveNodeStyle(node) {
    },
    createNodeGraphByNode(node) {
      if (node && node.type && this['create' + node.type + 'GraphNode']) {
        return this['create' + node.type + 'GraphNode'](node);
      }
    },
    contextItemClick(contextItem) {
      this.nodeAction = contextItem.id;
      if (contextItem.id === 'createNode') {
        this.$refs.UserTaskModal.setData(this.createUserTaskNode());
        this.$refs.ContextModal.close();
      }
      if (contextItem.id === 'insertNode') {
        this.$refs.UserTaskModal.setData(this.createUserTaskNode());
        this.$refs.ContextModal.close();
      }
      if (contextItem.id === 'addParallelNode') {
        this.$refs.UserTaskModal.setData(this.createUserTaskNode());
        this.$refs.ContextModal.close();
      }
      if (contextItem.id === 'removeNode') {
        this.removeSelectedNode(this.clickingNode);
        this.$refs.ContextModal.close();
      }
      if (contextItem.id === 'propertyConfig') {
        let node = this.getTaskById(this.clickingNode);
        this.$refs.UserTaskPropertiesModal.setData(node, this.formDefinition);
        this.$refs.ContextModal.close();
      }
      if (contextItem.id === 'addEmptyNode') {
        this.addEmptyNode();
        this.$refs.ContextModal.close();
      }
    },
    addEmptyNode() {
      let task = this.getTaskById(this.clickingNode);
      let before = this.getBeforeNodes(task)[0];
      let after = this.getAfterNodes(task)[0];
      let node = {
        id: guid(),
        type: 'empty',
        name: ''
      };
      this.flowModel.nodes.push(node);
      if (this.isPairGateWay(before, after)) {
        this.addLineByNode(before, node);
        this.addLineByNode(node, after);
      } else {
        let {startGateway, endGateway, gatewayPair} = this.genGatewayPair();
        this.flowModel.nodes.push(startGateway, endGateway);
        this.flowModel.gatewayPairs.push(gatewayPair);

        this.getBeforeLineByNodeId(task.id)[0].end = startGateway.id;
        this.getAfterLineByNodeId(task.id)[0].start = endGateway.id;

        this.addLineByNode(startGateway, node);
        this.addLineByNode(node, endGateway);

        this.addLineByNode(startGateway, task);
        this.addLineByNode(task, endGateway);
      }

      this.renderFlowModel();
      this.$nextTick(this.renderFlowModel);
    },
    addLineByNode(start, end) {
      this.flowModel.lines.push({
        id: guid(),
        start: start.id,
        end: end.id,
      });
    },
    addLineByNodeId(startId, endId) {
      this.flowModel.lines.push({
        id: guid(),
        start: startId,
        end: endId,
      });
    },
    getBeforeLineByNodeId(id) {
      let temp = [], lines = this.flowModel.lines;
      for (let i = 0; i < lines.length; i++) {
        if (id === lines[i].end) {
          temp.push(lines[i]);
        }
      }
      return temp;
    },
    getAfterLineByNodeId(id) {
      let temp = [], lines = this.flowModel.lines;
      for (let i = 0; i < lines.length; i++) {
        if (id === lines[i].start) {
          temp.push(lines[i]);
        }
      }
      return temp;
    },
    removeSelectedNode(taskId) {
      let clickingTask = this.getTaskById(taskId);
      if (this.inCount(clickingTask) === 1
          && this.outCount(clickingTask) === 1
          && this.isPairGateWay(this.getBeforeNodes(clickingTask)[0], this.getAfterNodes(clickingTask)[0])) {
        // 所有相关的线直接删除即可
        this.flowModel.lines = this.flowModel.lines.filter(line => {
          return !(line.start === taskId || line.end === taskId);
        });
      } else if (this.inCount(clickingTask) === 1 && this.outCount(clickingTask) === 1) {
        this.flowModel.lines.push({
          id: guid(),
          start: this.getBeforeNodes(clickingTask)[0].id,
          end: this.getAfterNodes(clickingTask)[0].id
        });
        this.flowModel.lines = this.flowModel.lines.filter(line => {
          return !(line.start === taskId || line.end === taskId);
        });
      }

      this.flowModel.nodes = this.flowModel.nodes.filter(line => {
        return !(line.id === taskId);
      });

      while (this.removeRedundanceGateWays()) ;
      this.renderFlowModel();
    },
    removeRedundanceGateWays() {
      let flag = false;
      let gatewayPairs = this.flowModel.gatewayPairs;
      for (let i = 0; i < gatewayPairs.length; i++) {
        let startGateWay = this.getTaskById(gatewayPairs[i].start);
        let endGateway = this.getTaskById(gatewayPairs[i].end);
        if (this.outCount(startGateWay) === 1) {
          flag = true;
          gatewayPairs.splice(i--, 1);

          let newLineStart = this.getBeforeNodes(startGateWay)[0].id;
          let newLineEnd = this.getAfterNodes(startGateWay)[0].id;
          this.flowModel.lines = this.flowModel.lines.filter(line => {
            return !(line.start === startGateWay.id || line.end === startGateWay.id);
          });
          this.flowModel.nodes = this.flowModel.nodes.filter(line => {
            return !(line.id === startGateWay.id);
          });
          this.flowModel.lines.push({
            id: guid(),
            start: newLineStart,
            end: newLineEnd,
          });

          newLineStart = this.getBeforeNodes(endGateway)[0].id;
          newLineEnd = this.getAfterNodes(endGateway)[0].id;
          this.flowModel.lines = this.flowModel.lines.filter(line => {
            return !(line.start === endGateway.id || line.end === endGateway.id);
          });
          this.flowModel.nodes = this.flowModel.nodes.filter(line => {
            return !(line.id === endGateway.id);
          });
          this.flowModel.lines.push({
            id: guid(),
            start: newLineStart,
            end: newLineEnd,
          });
        }
      }
      return flag;
    },
    createUserTaskNode() {
      return {
        id: guid(),
        type: 'user',
        name: '用户审批',
        userConfigs: [],
        executeWay: 'single',
        pcViewId: this.formDefinition.views[0].id,
        mobileViewId: this.formDefinition.views[0].id,
        executeCompleteThreshold: 100
      };
    },
    createuserGraphNode(node) {
      return {
        id: node.id,
        node: {
          labelType: "html",
          label: `<div class="label user-label" data-user-task="${node.id}" title="${node.name}">${node.name}</div>`,
          padding: 0,
          class: 'user-node'
        }
      };
    },
    createemptyGraphNode(node) {
      return {
        id: node.id,
        node: {
          labelType: "html",
          label: `<div class="label empty-label" data-empty-task="${node.id}"></div>`,
          padding: 0,
          class: 'user-node'
        }
      };
    },
    creategatewayGraphNode(node) {
      let clazz = this.getGateWayClass(node);

      return {
        id: node.id,
        node: {
          labelType: "html",
          label: `<div class="label gateway-label ${clazz}"></div>`,
          padding: 0,
          class: `gateway-node ${clazz}`
        }
      };
    },
    getGateWayClass(node) {
      if (this.isGateWayStart(node)) {
        return 'gateway-label-start';
      }
      return 'gateway-label-end';
    },
    isGateWayStart(node) {
      let gatewayPairs = this.flowModel.gatewayPairs;
      for (let i = 0; i < gatewayPairs.length; i++) {
        if (gatewayPairs[i].start === node.id) return true;
      }
      return false;
    },
    createStartNode() {
      return {
        id: 'start',
        node: {
          labelType: "html",
          label: `<div class="label start-label">发起人</div>`,
          padding: 0,
          class: 'start-node'
        }
      };
    },
    createEndNode() {
      return {
        id: 'end',
        node: {
          labelType: "html",
          label: '<div class="label end-label">结束</div>',
          padding: 0,
          class: 'end-node'
        }
      };
    },
    bindActions() {
      $(`#${this.containerID} .start-label`).click(this.startNodeClick);
      $(`#${this.containerID} .end-label`).click(this.endNodeClick);
      $(`#${this.containerID} .user-label`).click(this.userTaskClick);
      $(`#${this.containerID} .empty-label`).click(this.emptyNodeClick);
      $(`#${this.containerID} .line-formula-config-icon`).click(this.lineFormulaConfiguationClick);
    },
    lineFormulaConfiguationClick(e) {
      let target = e.target;
      this.clickingLine = null;
      while (target && !this.clickingLine) {
        this.clickingLine = target.getAttribute('data-line-id');
        target = target.parentNode;
      }

      let line = this.getLineById(this.clickingLine);
      this.$refs.LineFormulaModal.setData(this.formDefinition, line);
    },
    userTaskClick(e) {
      this.clickingNode = e.target.getAttribute('data-user-task');
      this.openContextModal(this.userTaskContext, e.clientX, e.clientY);
    },
    emptyNodeClick(e) {
      this.clickingNode = e.target.getAttribute('data-empty-task');
      this.openContextModal(this.emptyNodeContext, e.clientX, e.clientY);
    },
    startNodeClick(e) {
      this.openContextModal(this.startContext, e.clientX, e.clientY);
      this.clickingNode = 'start';
    },
    endNodeClick(e) {
      this.openContextModal(this.endContext, e.clientX, e.clientY);
      this.clickingNode = 'end';
    },
    openContextModal(context, x, y) {
      this.$refs.ContextModal.setData(context, x, y);
    },
    addUserTask(userTask) {
      if (this.clickingNode === 'start') {
        this.insertUserTaskAfter(userTask, 'start');
      } else if (this.clickingNode === 'end') {
        this.insertUserTaskBefore(userTask, 'end');
      } else {
        if (this.nodeAction === 'createNode') {
          this.insertUserTaskAfter(userTask, this.clickingNode);
        }
        if (this.nodeAction === 'insertNode') {
          this.insertUserTaskBefore(userTask, this.clickingNode);
        }
        if (this.nodeAction === 'addParallelNode') {
          this.addParallelNode(userTask, this.clickingNode);
        }
      }
    },
    lineFormulaModalOK(line) {
      console.log(line);
      // this.flowModel.lines = this.flowModel.lines.filter(l => {
      //   return l.id !== line.id;
      // });
      // this.flowModel.lines.push(line);

      this.renderFlowModel();
      this.$nextTick(this.renderFlowModel);
    },
    userTaskPropertiesModalOK(userTask) {
      this.flowModel.nodes = this.flowModel.nodes.filter(n => n.id !== userTask.id);
      this.flowModel.nodes.push(userTask);

      this.renderFlowModel();
      this.$nextTick(this.renderFlowModel);
    },
    addParallelNode(userTask, taskId) {
      let clickingTask = this.getTaskById(taskId);
      if (!clickingTask) return;
      // 增加一个节点
      this.flowModel.nodes.push(userTask);

      if (this.inCount(clickingTask) === 1
          && this.outCount(clickingTask) === 1
          && this.isPairGateWay(this.getBeforeNodes(clickingTask)[0], this.getAfterNodes(clickingTask)[0])) {
        // 简单插入即可
        let lines = this.flowModel.lines;
        lines.push({
          id: guid(),
          start: this.getBeforeNodes(clickingTask)[0].id,
          end: userTask.id,
        });
        lines.push({
          id: guid(),
          start: userTask.id,
          end: this.getAfterNodes(clickingTask)[0].id,
        });
      } else {
        // 需要生成网关然后插入
        let {startGateway, endGateway, gatewayPair} = this.genGatewayPair();
        this.flowModel.nodes.push(startGateway, endGateway);
        this.flowModel.gatewayPairs.push(gatewayPair);

        let lines = this.flowModel.lines;
        for (let i = 0; i < lines.length; i++) {
          if (lines[i].end === clickingTask.id) {
            lines[i].end = startGateway.id;
          }
          if (lines[i].start === clickingTask.id) {
            lines[i].start = endGateway.id;
          }
        }
        lines.push({
          id: guid(),
          start: startGateway.id,
          end: clickingTask.id,
        });
        lines.push({
          id: guid(),
          start: startGateway.id,
          end: userTask.id,
        });

        lines.push({
          id: guid(),
          start: clickingTask.id,
          end: endGateway.id,
        });
        lines.push({
          id: guid(),
          start: userTask.id,
          end: endGateway.id,
        });
      }
      this.renderFlowModel();
      this.$nextTick(this.renderFlowModel);
    },
    genGatewayPair() {
      let startGatewayId = guid();
      let endGatewayId = guid();
      return {
        startGateway: {
          id: startGatewayId,
          type: 'gateway'
        },
        endGateway: {
          id: endGatewayId,
          type: 'gateway'
        },
        gatewayPair: {
          start: startGatewayId,
          end: endGatewayId
        }
      };
    },
    isPairGateWay(gateway1, gateway2) {
      let gatewayPairs = this.flowModel.gatewayPairs;
      for (let i = 0; i < gatewayPairs.length; i++) {
        if (gatewayPairs[i].start === gateway1.id && gatewayPairs[i].end === gateway2.id) {
          return true;
        }
        if (gatewayPairs[i].end === gateway1.id && gatewayPairs[i].start === gateway2.id) {
          return true;
        }
      }
      return false;
    },
    getTaskById(taskId) {
      if (taskId === 'start') return {id: 'start'};
      if (taskId === 'end') return {id: 'end'};
      for (let i = 0; i < this.flowModel.nodes.length; i++) {
        if (this.flowModel.nodes[i].id === taskId) {
          return this.flowModel.nodes[i];
        }
      }
    },
    getAfterNodes(task) {
      let list = [];
      for (let i = 0; i < this.flowModel.lines.length; i++) {
        if (this.flowModel.lines[i].start === task.id) {
          list.push(this.getTaskById(this.flowModel.lines[i].end));
        }
      }
      return list;
    },
    getBeforeNodes(task) {
      let list = [];
      for (let i = 0; i < this.flowModel.lines.length; i++) {
        if (this.flowModel.lines[i].end === task.id) {
          list.push(this.getTaskById(this.flowModel.lines[i].start));
        }
      }
      return list;
    },
    inCount(task) {
      let count = 0;
      for (let i = 0; i < this.flowModel.lines.length; i++) {
        if (this.flowModel.lines[i].end === task.id) {
          count++;
        }
      }
      return count;
    },
    outCount(task) {
      let count = 0;
      for (let i = 0; i < this.flowModel.lines.length; i++) {
        if (this.flowModel.lines[i].start === task.id) {
          count++;
        }
      }
      return count;
    },
    insertUserTaskBefore(userTask, taskId) {
      // 增加一个节点
      this.flowModel.nodes.push(userTask);

      // 把start直接指向新的节点， 新的节点直接指向start后面的节点
      let lines = this.flowModel.lines;
      for (let i = 0; i < lines.length; i++) {
        if (lines[i].end === taskId) lines[i].end = userTask.id;
      }

      lines.push({
        id: guid(),
        start: userTask.id,
        end: taskId
      });
      this.renderFlowModel();
      this.$nextTick(this.renderFlowModel);
    },
    insertUserTaskAfter(userTask, taskId) {
      // 增加一个节点
      this.flowModel.nodes.push(userTask);

      // 把start直接指向新的节点， 新的节点直接指向start后面的节点
      let lines = this.flowModel.lines;
      for (let i = 0; i < lines.length; i++) {
        if (lines[i].start === taskId) lines[i].start = userTask.id;
      }

      lines.push({
        id: guid(),
        start: taskId,
        end: userTask.id
      });
      this.renderFlowModel();
      this.$nextTick(this.renderFlowModel);
    },
    getLineById(lineId) {
      let lines = this.flowModel.lines;
      for (let i = 0; i < lines.length; i++) {
        if (lineId === lines[i].id) {
          return lines[i];
        }
      }
      return null;
    },
    drawBackground() {
    },
  },
  computed: {
    svgStyle() {
      return {
        width: `${this.svgWidth}px`,
        'height': `100%`
      };
    },
  }
};
</script>

<style scoped>
.container {
  background: #e0e0e0;
  position: relative;
}

.flow-image {
  min-width: 100%;
  min-height: 500px;
  height: calc(100vh - 54px);
}

.flow-model-container-editable {
  height: calc(100vh - 54px);
  overflow: auto;
}
</style>
